package com.chx.mydubbo.client;

import com.alibaba.dubbo.common.io.Bytes;
import com.alibaba.fastjson.JSON;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author 夏天
 * @date 2018/4/28 下午4:39
 * @Description: 模拟一个简单的dubbo客户端
 */
public class MyDubboClient {

    private String ip;
    private int port;

    public MyDubboClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }


    public Object dubboConsumer(String interfaceName, String version, String methodName, String paramsKeys, String paramsValues) throws Exception {
        SocketChannel socket = SocketChannel.open();
        // 连接dubbo服务器
        socket.connect(new InetSocketAddress(ip, port));

        // --------------组织请求报文--------------
        // ########################### 1. body 很多数据，每一部分的数据是通过 \r\n 表示
        StringBuffer bodyStr = new StringBuffer();
        // 1.1 RPC的版本号（最新稳定dubbo 2.5.10）
        bodyStr.append(JSON.toJSONString("2.5.3")).append("\r\n");
        // 1.2 接口
        bodyStr.append(JSON.toJSONString(interfaceName)).append("\r\n");
        // 1.3 服务版本号
        bodyStr.append(JSON.toJSONString(version)).append("\r\n");
        // 1.4 方法名
        bodyStr.append(JSON.toJSONString(methodName)).append("\r\n");
        // 1.5 参数类型（JVM参数描述）
        bodyStr.append(JSON.toJSONString(paramsKeys)).append("\r\n");
        // 1.6 参数值（客户端传递）
        bodyStr.append(JSON.toJSONString(paramsValues)).append("\r\n");
        // 1.7 隐藏参数（dubbo框架拓展提供...）
        bodyStr.append("{}").append("\r\n");

        byte[] body = bodyStr.toString().getBytes();

        System.out.println("------------------->bodyStr：");
        System.out.println(bodyStr);


        // ######################### 2. header      固定长度 16字节
        byte[] header = new byte[16];
        // 2.1 Magic：一个固定的特殊标志   2字节16位（正好是一个short数据类型的长度）   16进制写法
        short magic = (short) 0xdabb;
        byte[] magicArray = Bytes.short2bytes(magic);
        System.arraycopy(magicArray, 0, header, 0, 2);
        // 2.2 Flag：表示数据包时  请求/响应
        byte flag = (byte) 0xC6;
        header[2] = flag;
        // 2.3 Status：请求执行后的响应状态（如果是请求数据包，那就写一个 0）
        header[3] = 0x00;
        // 2.4 messageId： 序列号，第几次请求
        long seq = 1L;
        // 长度 8个字节
        byte[] seqArray = Bytes.long2bytes(seq);
        System.arraycopy(seqArray, 0, header, 4, 8);
        // 2.5 bodyLength：Body内容的长度
        int bodyLength = body.length;
        // 长度 4个字节
        byte[] bodyLengthArray = Bytes.int2bytes(bodyLength);
        System.arraycopy(bodyLengthArray, 0, header, 12, 4);

        System.out.println("----->请求magic：" + Integer.toBinaryString(magic).substring(15, 31));
        System.out.println("----->请求flag：" + Integer.toBinaryString(flag).substring(23, 31));

        // 完整请求报文
        byte[] request = new byte[header.length + body.length];
        System.arraycopy(header, 0, request, 0, 16);
        System.arraycopy(body, 0, request, 16, body.length);

        System.out.println("header+body组装完成，通过socket 传输到dubbo-server服务提供者");

        socket.write(ByteBuffer.wrap(request));
        System.out.println("----->数据已经发送给dubbo");


        System.out.println();
        System.out.println("------------------------------>等待dubbo-server应答<-----------------------------------");
        System.out.println();


        // 接受server的返回值（header+body 包含方法执行的结果）
        ByteBuffer headerResponse = ByteBuffer.allocate(16);
        socket.read(headerResponse);

        // 解析header内容   header      固定长度 16字节
        // 2.1 Magic：一个固定的特殊标志   2字节   16进制写法
        byte[] magicResponseArray = new byte[2];
        System.arraycopy(headerResponse.array(), 0, magicResponseArray, 0, 2);
        short magicResponse = Bytes.bytes2short(magicResponseArray);
        System.out.println("<-----响应magic二进制：" + Integer.toBinaryString(magicResponse).substring(15, 31));

        // 2.2 Flag：表示数据包时  请求/响应
        // flag：标志位, 一共8个地址位。低四位用来表示消息体数据用的序列化工具的类型（默认hessian），
        // 高四位中，第一位为1表示是request请求，第二位为1表示双向传输（即有返回response），第三位为1表示是心跳ping事件。
        byte flagResponse = headerResponse.array()[2];
        System.out.println("<-----响应flag十进制：" + flagResponse);
        // 请求：1110 0011    回应：0000 0010
        // String tString = Integer.toBinaryString((flagResponse & 0xFF) + 0x100).substring(1);
        System.out.println("<-----响应flag二进制：" + Integer.toBinaryString(flagResponse));

        // 2.3 Status：请求执行后的响应状态（如果是请求数据包，那就写一个 0）
        // status：状态位, 设置请求响应状态，dubbo定义了一些响应的类型。具体类型见com.alibaba.dubbo.remoting.exchange.Response
        byte statusResponse = headerResponse.array()[3];
        System.out.println("<-----响应status十进制：" + statusResponse);

        // 2.4 messageId： 序列号，第几次请求
        byte[] messageIdResponseArray = new byte[8];
        System.arraycopy(headerResponse.array(), 4, messageIdResponseArray, 0, 8);
        long messageIdResponse = Bytes.bytes2long(messageIdResponseArray);
        System.out.println("<-----响应messageId十进制：" + messageIdResponse);

        // 2.5 bodyLength：Body内容的长度
        byte[] bodyLengthResponseArray = new byte[4];
        System.arraycopy(headerResponse.array(), 12, bodyLengthResponseArray, 0, 4);
        int bodyLengthResponse = Bytes.bytes2int(bodyLengthResponseArray);
        System.out.println("<-----响应bodyLength十进制：" + bodyLengthResponse);


        // 长度实际上在header已经声明
        ByteBuffer bodyResponse = ByteBuffer.allocate(bodyLengthResponse);
        socket.read(bodyResponse);

        System.out.println("bodyResponseLength###########-->" + bodyResponse.array().length);

        // bodyMagic
        byte[] bodyMagicArray = new byte[2];
        System.arraycopy(bodyResponse.array(), 0, bodyMagicArray, 0, 2);
        short bodyMagic = Bytes.bytes2short(bodyMagicArray);
        System.out.println("<-----响应bodyMagic十进制：" + bodyMagic);

        byte[] bodyArray = new byte[bodyLengthResponse - 2];
        System.arraycopy(bodyResponse.array(), 2, bodyArray, 0, bodyLengthResponse - 2);
        System.out.println("<-----响应中body：\r\n" + new String(bodyArray));


        // 一直不关闭，就是长链接
        // Thread.sleep(9999999);
        socket.close();

        //
        return new String(bodyArray);
    }

}
